import klawSync from "klaw-sync";
import Path from "path";
import { __projectDir } from "../node";
import { camelCase, upperFirst } from "lodash-es";
import Fs from "fs-extra";
import chokidar from "chokidar";
import chalk from "chalk";

// 默认的查找vue组件的目录
export const defaultDirPath = Path.normalize(
  Path.join(__projectDir, "vue/components")
);

// 默认的生成的.d.ts文件的绝对路径
export const defaultDtsPath = Path.normalize(
  Path.join(__projectDir, "./vue/components/components.d.ts")
);

/**
 * 获取Component的绝对路径
 * @param {Object} param0 组件的目录
 * @param {String} param0.dir 组件的目录
 * @returns {string[]} 组件的绝对路径
 */
export function getComponentPaths({ dir = defaultDirPath } = {}) {
  return klawSync(dir)
    .filter((e) => /\.vue/.test(e.path))
    .map((e) => e.path);
}

/**
 * 根据绝对路径获取对应的import路径
 * @param {string} componentPath 该component的绝对路径
 * @param {string} dtsPath .d.ts文件的绝对路径
 * @returns {string} 对应的import路径
 */
export function getImportPath(componentPath: string, dtsPath = defaultDtsPath) {
  let result = Path.relative(Path.dirname(dtsPath), componentPath);
  result = result.replace(/\\/g, "/");
  if (!/\.\//g.test(result)) {
    result = "./" + result;
  }
  return result;
}

/**
 * 获取组件名称，用CamelCase表示
 * @param {String} path 组件的绝对路径
 * @returns {String} 组件名称
 */
export function getComponentName(path: string) {
  const fileName = Path.basename(path, ".vue");
  const name = upperFirst(camelCase(fileName));
  return name;
}
/**
 * 获取该组件的行的表达式
 * @param {String} path 组件的绝对路径
 * @param {String} dtsPath .d.ts文件的绝对路径
 * @returns {String} 该组件的行的表达式
 */
export function getComponentRow(path: any, dtsPath = defaultDtsPath) {
  const importPath = getImportPath(path, dtsPath);
  const name = getComponentName(path);
  return `    ${name}: typeof import("${importPath}")["default"];`;
}

/**
 * 获取多个组件的行的表达式数组
 * @param {String[]} paths 组件的绝对路径
 * @param {string} dtsPath .d.ts文件的绝对路径
 * @returns {String[]} 多个组件的行的表达式数组
 */
export function getComponentRows(paths: any[], dtsPath = defaultDtsPath) {
  return paths.map((path: any) => getComponentRow(path, dtsPath));
}

/**
 * GlobalComponents内部的表达式
 * @param {String[]} paths 组件的绝对路径
 * @param {String} dtsPath .d.ts文件的绝对路径
 * @returns {String} GlobalComponents内部的表达式
 */
export function getComponentRowsStr(paths: string[], dtsPath = defaultDtsPath) {
  return getComponentRows(paths, dtsPath).join("\n");
}

/**
 *
 * @param {Object} param0
 * @param {String[]} param0.paths 组件绝对路径
 * @param {String} param0.dtsPath .d.ts文件的绝对路径
 * @returns {void} 写入文件
 */
export function getDeclareString({
  paths = getComponentPaths(),
  dtsPath = defaultDtsPath,
} = {}) {
  let content = getComponentRowsStr(paths, dtsPath);
  let toWrite = `// auto-generated by gen-component-types.mjs
import "@vue/runtime-core";
declare module "@vue/runtime-core" {
  export interface GlobalComponents {
    // --- ↓ components ↓ ---
${content}
    // --- ↑ components ↑ ---
  }
}
`;
  return toWrite;
}

export function writeDts(
  componentDir = defaultDirPath,
  dtsPath = defaultDtsPath
) {
  let componentPaths = getComponentPaths({ dir: componentDir });
  let content = getDeclareString({ paths: componentPaths, dtsPath });
  Fs.writeFileSync(dtsPath, content);
}

export interface WatchBuildOption {
  /** Where are the components located (directory path) */
  whereAreTheComponents?: string;
  /** Where is the .d.ts file located (file path) */
  whereToPlaceTheDts?: string;
}

/**
 * 用于监听文件改动并构建components.d.ts文件
 * @param param0 文件和文件夹位置
 */
export function watchBuild({
  whereAreTheComponents = defaultDirPath,
  whereToPlaceTheDts = defaultDtsPath,
}: WatchBuildOption = {}): chokidar.FSWatcher {
  return chokidar.watch(whereAreTheComponents).on("all", (event, path) => {
    if (path !== whereToPlaceTheDts) {
      console.log(chalk.blue(`[${event}]: ${path}`));
      writeDts(whereAreTheComponents, whereToPlaceTheDts);
    }
  });
}

export default watchBuild;
